昨天提到了Go併發中通道Channel
交換訊息的方法,但世界總是沒有想像中的美好。
有些被併發出去的func
做的事情少,很快就回來了,有些則慢吞吞...
要怎麼知道他們到底完事了沒有?
可以透過內建的sync WaitGroup
來等待線程結束,
就像一群新兵在準備集合,等到每個人都到為止。
A WaitGroup waits for a collection of goroutines to finish.
以下是班長與兩位班兵的角色:
https://play.golang.org/p/bQowTuEzeIj
func main() {
fmt.Println("你各位啊,現在開始休息,三秒鐘後記得回來。")
wg := sync.WaitGroup{} // 也可以var wg = sync.WaitGroup{},或者不要實體化 var wg sync.WaitGroup
wg.Add(2) // 總共有兩位新兵
go rest(&wg)
go rest(&wg)
fmt.Println("===你各位再慢慢來沒關係啊===")
wg.Wait()
fmt.Println("===集合完畢===")
}
func rest(wg *sync.WaitGroup) {
time.Sleep(time.Second * 3)
fmt.Println("新兵休息完畢。")
wg.Done() // 跑去集合
}
/* result:
你各位啊,現在開始休息,三秒鐘後記得回來。
===你各位再慢慢來沒關係啊===
新兵休息完畢。
新兵休息完畢。
===集合完畢===
*/
WaitGroup拿計數器(Counter)
來當作任務數量,若counter < 0
會發生panic
。
+n
減去1
,可搭配defer
使用歸0
如果計數器大於線程數就會發生死結(Deadlock)
。
啊兵就只有兩隻,等到死還是只有這麼多隻,永遠沒辦法集合完畢。
因為是針對該鎖的物件
操作,記得是要傳入func指針(Pointer)
與位址(Address)
。
看起來方便好用,貌似能解決一切的問題。
但不負所望地,世界還是沒有你想像的美好,...
在這個例子中我使用了10000
個被併發出去的func
,
每個func
只做一件事:count++
https://play.golang.org/p/OAaaLw8Po62
var count = 0
func main() {
// runtime.GOMAXPROCS(1) // 只讓一個線程運作就能解決問題。但...想要更快,就是要多核心嘛!單核心怎能星爆?
for i := 0; i < 10000; i++ {
go race()
}
time.Sleep(time.Millisecond * 100)
fmt.Println(count)
}
func race() {
count++
}
/* result:
9763
*/
什麼?輸出居然不是10000
?
但如果數字小一點,例如10
,就又恢復正常了?
這就是爭奪資源的關係。
物競天擇,在這凡事都講求時效性的年代,手腳比較快的就容易成功弄破碗。
多個CPU
在搶奪count
這個變數,好比多個男性在追求一個異性:
同時有兩個男性問:「小姊,請問您單身嗎?」
『是的,我目前單身哦。』於是她同時和兩位男性交往。
該如何對付呢?
會面遇到的上面問題,是由於同時對變數進行讀寫(Read/Write)的關係,
在小故事中則是同時被問與答。
同時有兩位男性警察詢問:「小姊,請問您單身嗎?」
『是的,我目前單身哦。』此時...
由於員警A學過互斥鎖的原理,知道這裡若慢了一步,一切就完了
於是以迅雷不及掩耳之勢拿出手銬腳鐐,把愛慕對象鎖住帶回偵辦而員警B沒有好好念書,只能原地傻傻乾等,悔恨莫及..
If the lock is already in use, the calling goroutine blocks until the mutex is available.
var count = 0
var m sync.Mutex
func main() {
for i := 0; i < 10000; i++ {
go race()
}
time.Sleep(time.Millisecond * 100)
fmt.Println(count)
}
func race() {
m.Lock()
count++
m.Unlock()
}
/* result:
10000
*/
只要在變數前上鎖(Lock),在解鎖(Unlock)前 只有該線程能對其進行操作。